// #packager-optimize

(function() {

    var $u = juliet.using({
        func : 'juliet.core.func-0.1',
        dom : 'juliet.core.dom-0.1',
        Observer : 'juliet.core.Observer-0.1',
        object : 'juliet.core.object-0.1'
    });

    /**
     * Used to make UI elements droppable.
     */
    var Drop = function() {

        /**
         * Options object passed on "initialize" method with default values for unset parameters.
         */
        this.options = { };

        this.activeDropElementData = null;

        /**
         * Generates and handles events.
         */
        this.observer = null;

        /**
         * Proxy for drop element.
         */
        this.proxy = null;

        this.dragElementAcceptClass = null;

        this.updateDragElementAcceptClass = function() {
            if ( this.dragElementAcceptClass != null ) return;

            var
            dragElementClassName = this.options.drag.activeDragElementData.elementDom.className,
            dragAccept;

            for (dragAccept in this.options.accept) {
                if ( dragElementClassName.indexOf(dragAccept) != -1 ) {
                    this.dragElementAcceptClass = dragAccept;
                    return;
                }
            }

            this.dragElementAcceptClass = '';
        };

        /**
         * Checks if the dropping element has correct class.
         */
        this.isCorrectDropElement = function(el) {
            this.updateDragElementAcceptClass();

            return (el && (el != this.options.drag.activeDragElementData.elementDom)
                && el.className && (el.className.indexOf(this.options.itemClass) != -1)
                && (el.className.indexOf(this.options.accept[this.dragElementAcceptClass]) != -1));
        };

        this.isProxyElement = function(el) {
            return (el && el.className && (el.className.indexOf(this.proxy.AREA_CLASS_NAME_POSTFIX) != -1));
        };

        /**
         * Prepares drop area proxy element before shows it.
         */
        this.prepareAndShowProxy = function(e) {
            var
            activeDropElement = $(this.activeDropElementData.elementDom),
            offset = activeDropElement.offset();

            this.proxy.setOffset(offset.left, offset.top);
            this.proxy.setSize(activeDropElement.outerWidth(), activeDropElement.outerHeight());
            this.proxy.hightlightArea(null);
            this.proxy.show();
        };

        this.resetDrop = function() {
            this.proxy.hide();
            this.activeDropElementData = null;
            this.dragElementAcceptClass = null;
        };

        this.fixTarget = function(target) {
            return ($u('dom').parent(target, this.options.itemClass, 3) || target);
        };

        this.onMouseClick = function() {
            $(document).unbind('click', $u('func').proxy(this.onMouseClick));
            return false;
        };

        this.preventClickEventOnDrop = function(e) {
            var
            target = this.fixTarget(e.target),
            activeDragElementData = this.options.drag.activeDragElementData;

            if ( !activeDragElementData.dragging || (target != activeDragElementData.elementDom) ) {
                return;
            }

            $(document).click($u('func').proxy(this.onMouseClick, this));
        };

        /**
         * Handles drag object mouse up event.
         */
        this.onMouseUp = function(e) {
            if ( !this.activeDropElementData ) {
                this.preventClickEventOnDrop(e);
                return;
            }

            this.observer.notify('onDropFinished', {
                layout : this.proxy.getHightlightedAreaLayout(),
                activeDragElementDom : this.options.drag.activeDragElementData.elementDom,
                activeDropElementDom : this.activeDropElementData.elementDom
            });

            this.resetDrop();
        };

        /**
         * Handles drag object mouse move event.
         */
        this.onMouseMove = function(e) {
            var
            target = this.fixTarget(e.target),
            isProxy = this.isProxyElement(target);

            if ( !isProxy && !this.isCorrectDropElement(target) ) {
                this.resetDrop();
                return;
            }

            if ( isProxy ) {
                this.proxy.hightlightArea(target.className);
                return;
            }

            this.activeDropElementData = { elementDom : target };
            this.prepareAndShowProxy(e);
        };

        this.initializeDragEventsListener = function() {
            var observer = this.options.drag.observer;

            observer.subscribe('onMouseMove', this.onMouseMove, this);
            observer.subscribe('onMouseUp', this.onMouseUp, this);
        };

        /**
         * Initialize instance using options
         */
        this.initialize = function(o) {
            this.proxy = juliet.createObject($u('DropProxy'));
            this.observer = juliet.createObject($u('Observer'));

            $u('object').merge(this.options, o);

            this.initializeDragEventsListener();
            this.proxy.initialize();
        };

    };

    juliet.registerModule('juliet.ui.dragAndDrop', 'Drop-0.1', Drop);

})();
